/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PlayerNative.h"
#include <chrono>
#include <cstdint>
#include <cstdio>

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xFF00
#define LOG_TAG "player"

using namespace std::chrono_literals;

namespace {
static constexpr int32_t REINIT_TIMES = 200;
static constexpr int32_t RESTART_TIMES = 0;
static constexpr int32_t TEST_DIFFERENT_AUDIO_FORMAT_COUNT = 8;
static constexpr int32_t TEST_AAC_INDEX_FIRST = 4;
static constexpr int32_t TEST_AAC_INDEX_SECOND = 7;
}



struct CallbackContext {
    napi_env env = nullptr;
    napi_ref callbackRef = nullptr;
};

void Callback(void *asyncContext) {
    uv_loop_s *loop = nullptr;
    CallbackContext *context = (CallbackContext *)asyncContext;
    napi_get_uv_event_loop(context->env, &loop);
    uv_work_t *work = new uv_work_t;
    work->data = context;
    uv_queue_work(
        loop, work, [](uv_work_t *work) {},
        [](uv_work_t *work, int status) {
            CallbackContext *context = (CallbackContext *)work->data;
            napi_handle_scope scope = nullptr;
            // Manage the lifecycle of napi_value to prevent memory leaks.
            napi_open_handle_scope(context->env, &scope);
            napi_value callback = nullptr;
            napi_get_reference_value(context->env, context->callbackRef, &callback);
            // Callback to UI side.
            napi_call_function(context->env, nullptr, callback, 0, nullptr, nullptr);
            napi_close_handle_scope(context->env, scope);
            delete context;
            delete work;
        });
}

std::shared_ptr<IAVDecoderEngine> PlayerNative::decoderEngine_ = nullptr;
std::shared_ptr<IAVEncoderEngine> PlayerNative::encoderEngine_ = nullptr;
std::shared_ptr<DecodeCallback> PlayerNative::decodeCb_ = nullptr;
std::shared_ptr<EncodeCallback> PlayerNative::encodeCb_ = nullptr;
bool PlayerNative::isPreview_ = false;
std::condition_variable PlayerNative::catchEOSCond_;
std::mutex PlayerNative::catchEOSMutex_;
bool PlayerNative::isEOSCatched_;

std::shared_ptr<IAVDecoderEngine> PlayerNative::decoderEngineConcurrency_ = nullptr;
std::shared_ptr<IAVEncoderEngine> PlayerNative::encoderEngineConcurrency_ = nullptr;
std::shared_ptr<DecodeCallbackConcurrency> PlayerNative::decodeCbConcurrency_ = nullptr;
std::shared_ptr<EncodeCallback> PlayerNative::encodeCbConcurrency_ = nullptr;
bool PlayerNative::isPreviewConcurrency_ = false;
std::condition_variable PlayerNative::catchEOSCondConcurrency_;
std::mutex PlayerNative::catchEOSMutexConcurrency_;
bool PlayerNative::isEOSCatchedConcurrency_;

napi_value PlayerNative::Play(napi_env env, napi_callback_info info) {
    SampleInfo sampleInfo;
    size_t argc = 4;
    napi_value args[4] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    napi_get_value_int32(env, args[0], &sampleInfo.inputFd);
    napi_get_value_int64(env, args[1], &sampleInfo.inputFileOffset);
    napi_get_value_int64(env, args[2], &sampleInfo.inputFileSize);

    auto asyncContext = new CallbackContext();
    asyncContext->env = env;
    napi_create_reference(env, args[3], 1, &asyncContext->callbackRef);

    sampleInfo.playDoneCallback = &Callback;
    sampleInfo.playDoneCallbackData = asyncContext;

//    FILE *fileMusic = fopen("data/storage/el2/base/haps/app/files/1.mp4", "r");
    FILE *fileMusic = fopen("data/storage/el2/base/haps/app/files/2.mp3", "r");
    int32_t fdMusic = -1;
    if (fileMusic != nullptr) {
        fdMusic = fileno(fileMusic);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    FILE *file = fopen("data/storage/el2/base/haps/app/files/result.mp4", "wb");
    int32_t fd = -1;
    if (file != nullptr) {
        fd = fileno(file);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    AVMuxerParam muxerParam;
    AVEncodeParam encodeParam;
    muxerParam.targetFileFd = fd;
    encodeParam.muxerParam = muxerParam;

    decodeCb_ = std::make_shared<DecodeCallback>();
    encodeCb_ = std::make_shared<EncodeCallback>();

    encoderEngine_ = IAVEncoderEngine::Create(encodeParam, encodeCb_);
    encoderEngine_->Init();
    OHNativeWindow *window = encoderEngine_->GetVideoInputWindow();
    std::shared_ptr<PcmBufferQueue> queue = encoderEngine_->GetAudioInputBufferQueue();
    // encoderEngine_->Start();

    decoderEngine_ = IAVDecoderEngine::Create(sampleInfo.inputFd, decodeCb_);
     decoderEngine_->SetVideoOutputWindow(NativeXComponentSample::PluginManager::GetInstance()->pluginWindow_);
//    isPreview_ = true;
    int32_t ret = decoderEngine_->Init();
    CHECK_AND_RETURN_RET_LOG(ret == 0, nullptr, "init failed");
    
    AVCODEC_SAMPLE_LOGI("start get video duration");
    int64_t videoDuration = decoderEngine_->GetVideoDuration();
    AVCODEC_SAMPLE_LOGI("video duration is %{public}lld", videoDuration / 1000);
    
    OH_AVFormat *videoFormat = decoderEngine_->GetVideoFormat();
    int64_t avDataDuration;
    OH_AVFormat_GetLongValue(videoFormat, OH_MD_KEY_DURATION, &avDataDuration);
    AVCODEC_SAMPLE_LOGI("avDataDuration is %{public}lld", avDataDuration / 1000);
    decoderEngine_->SetPlayDuration(videoDuration / 1000);
    if (fdMusic != -1) {
        decoderEngine_->AddAudio(fdMusic);
    }
//    decoderEngine_->Seek(4000, SEEK_MODE_CLOSEST_SYNC);
    isPreview_ = true;
//    encoderEngine_->Start();
    decoderEngine_->Start();

    // for test
    std::this_thread::sleep_for(2s);
    decoderEngine_->Pause();
//    decoderEngine_->AudioMute(true);
    std::this_thread::sleep_for(2s);
    decoderEngine_->Seek(0, SEEK_MODE_CLOSEST_SYNC);
//    std::this_thread::sleep_for(2s);
    decoderEngine_->Start();
//    decoderEngine_->DeleteAudio(fdMusic);
//    decoderEngine_->AudioMute(false);
    return nullptr;
}

napi_value PlayerNative::Export(napi_env env, napi_callback_info info) {
    SampleInfo sampleInfo;
    size_t argc = 4;
    napi_value args[4] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    napi_get_value_int32(env, args[0], &sampleInfo.inputFd);
    napi_get_value_int64(env, args[1], &sampleInfo.inputFileOffset);
    napi_get_value_int64(env, args[2], &sampleInfo.inputFileSize);

    auto asyncContext = new CallbackContext();
    asyncContext->env = env;
    napi_create_reference(env, args[3], 1, &asyncContext->callbackRef);

    sampleInfo.playDoneCallback = &Callback;
    sampleInfo.playDoneCallbackData = asyncContext;

//    FILE *fileMusic = fopen("data/storage/el2/base/haps/app/files/1.mp4", "r");
    FILE *fileMusic = fopen("data/storage/el2/base/haps/app/files/2.mp3", "r");
    int32_t fdMusic = -1;
    if (fileMusic != nullptr) {
        fdMusic = fileno(fileMusic);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    FILE *file = fopen("data/storage/el2/base/haps/app/files/result.mp4", "wb");
    int32_t fd = -1;
    if (file != nullptr) {
        fd = fileno(file);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    AVMuxerParam muxerParam;
    AVEncodeParam encodeParam;
    muxerParam.targetFileFd = fd;
    encodeParam.muxerParam = muxerParam;

    decodeCb_ = std::make_shared<DecodeCallback>();
    encodeCb_ = std::make_shared<EncodeCallback>();

    encoderEngine_ = IAVEncoderEngine::Create(encodeParam, encodeCb_);
    encoderEngine_->Init();
    OHNativeWindow *window = encoderEngine_->GetVideoInputWindow();
    std::shared_ptr<PcmBufferQueue> queue = encoderEngine_->GetAudioInputBufferQueue();
    // encoderEngine_->Start();

    decoderEngine_ = IAVDecoderEngine::Create(sampleInfo.inputFd, decodeCb_);
    decoderEngine_->SetVideoOutputWindow(window);
    isPreview_ = false;
    int32_t ret = decoderEngine_->Init();
    CHECK_AND_RETURN_RET_LOG(ret == 0, nullptr, "init failed");
    OH_AVFormat *videoFormat = decoderEngine_->GetVideoFormat();
    int64_t avDataDuration;
    OH_AVFormat_GetLongValue(videoFormat, OH_MD_KEY_DURATION, &avDataDuration);
    AVCODEC_SAMPLE_LOGI("avDataDuration is %{public}lld", avDataDuration / 1000);
//    decoderEngine_->AudioUnselect(true);
    decoderEngine_->SetHighPerformance(true);
    decoderEngine_->SetPlayDuration(avDataDuration / 1000);
    if (fdMusic != -1) {
        decoderEngine_->AddAudio(fdMusic);
    }
//    decoderEngine_->AudioUnselect(true);
//    decoderEngine_->Seek(4000, SEEK_MODE_CLOSEST_SYNC);
    decoderEngine_->SetVideoOutputWindow(window);
    decoderEngine_->SetAudioOutputBufferQueue(queue);
    encoderEngine_->Start();
    decoderEngine_->Start();

    return nullptr;
}

napi_value PlayerNative::Test(napi_env env, napi_callback_info info)
{
    SampleInfo sampleInfo;
    size_t argc = 4;
    napi_value args[4] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    napi_get_value_int32(env, args[0], &sampleInfo.inputFd);
    napi_get_value_int64(env, args[1], &sampleInfo.inputFileOffset);
    napi_get_value_int64(env, args[2], &sampleInfo.inputFileSize);

    auto asyncContext = new CallbackContext();
    asyncContext->env = env;
    napi_create_reference(env, args[3], 1, &asyncContext->callbackRef);

    sampleInfo.playDoneCallback = &Callback;
    sampleInfo.playDoneCallbackData = asyncContext;

    std::thread([sampleInfo]() {
        PlayerNative::TestInner(sampleInfo);
    }).detach();
    return nullptr;
}

napi_value PlayerNative::TestInner(SampleInfo sampleInfo) {

//    FILE *fileMusic = fopen("data/storage/el2/base/haps/app/files/1.mp4", "r");
    FILE *fileMusic = fopen("data/storage/el2/base/haps/app/files/2.mp3", "r");
    int32_t fdMusic = -1;
    if (fileMusic != nullptr) {
        fdMusic = fileno(fileMusic);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    FILE *file = fopen("data/storage/el2/base/haps/app/files/result.mp4", "wb");
    int32_t fd = -1;
    if (file != nullptr) {
        fd = fileno(file);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    AVMuxerParam muxerParam;
    AVEncodeParam encodeParam;
    muxerParam.targetFileFd = fd;
    encodeParam.muxerParam = muxerParam;

    decodeCb_ = std::make_shared<DecodeCallback>();
    encodeCb_ = std::make_shared<EncodeCallback>();

    TestPreviewSeek(sampleInfo, encodeParam, fdMusic);
    TestPreviewAddAudio(sampleInfo, encodeParam, fdMusic);
    TestPreviewMuteAudio(sampleInfo, encodeParam, fdMusic);
    TestExportSeek(sampleInfo, encodeParam, fdMusic);
    TestExportAddAudio(sampleInfo, encodeParam, fdMusic);
    TestExportUnselectAudio(sampleInfo, encodeParam, fdMusic);
    TestPreviewDifferentAudioFormats(sampleInfo, encodeParam, fdMusic);
    TestExportDifferentAudioFormats(sampleInfo, encodeParam, fdMusic);

    TestLongTimePreviewing(sampleInfo, encodeParam, fdMusic);

    TestLongTimeExporting(sampleInfo, encodeParam, fdMusic);

    TestLongTimeConcurrency(sampleInfo, encodeParam, fdMusic);
    TestDiffVideoFormatPreviewing(sampleInfo, encodeParam, fd);
    TestDiffVideoFormatExporting(sampleInfo, encodeParam, fd);

    
    if (file) {
        fclose(file);
    }
    if (fileMusic) {
        fclose(fileMusic);
    }
    return nullptr;
}

/**
 * @brief Wait for previewing done
 */
void PlayerNative::WaitForPreviewDone()
{
    std::unique_lock<std::mutex> lock(catchEOSMutex_);
    isEOSCatched_ = false;
    catchEOSCond_.wait(lock, []() { return isEOSCatched_; });
    std::this_thread::sleep_for(2s);
    decoderEngine_ = nullptr;
}

/**
 * @brief Wait for exporting done
 */
void PlayerNative::WaitForExportDone()
{
    std::unique_lock<std::mutex> lock(catchEOSMutex_);
    isEOSCatched_ = false;
    catchEOSCond_.wait(lock, []() { return isEOSCatched_; });
    std::this_thread::sleep_for(2s);
    encoderEngine_ = nullptr;
    decoderEngine_ = nullptr;
}

/**
 * @brief Prepare for previewing test
 */
void PlayerNative::TestPreviewPrepare(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    decoderEngine_ = IAVDecoderEngine::Create(sampleInfo.inputFd, decodeCb_);
    decoderEngine_->SetVideoOutputWindow(NativeXComponentSample::PluginManager::GetInstance()->pluginWindow_);
    isPreview_ = true;
    int32_t ret = decoderEngine_->Init();
    CHECK_AND_RETURN_LOG(ret == 0, "init failed");
    OH_AVFormat *videoFormat = decoderEngine_->GetVideoFormat();
    int64_t avDataDuration;
    OH_AVFormat_GetLongValue(videoFormat, OH_MD_KEY_DURATION, &avDataDuration);
    AVCODEC_SAMPLE_LOGI("avDataDuration is %{public}lld", avDataDuration / 1000);
    decoderEngine_->SetPlayDuration(avDataDuration / 1000);
    if (fd != -1) {
        decoderEngine_->AddAudio(fd);
    }
}

/**
 * @brief Prepare for exporting test
 */
void PlayerNative::TestExportPrepare(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    encoderEngine_ = IAVEncoderEngine::Create(encodeParam, encodeCb_);
    encoderEngine_->Init();
    OHNativeWindow *window = encoderEngine_->GetVideoInputWindow();
    std::shared_ptr<PcmBufferQueue> queue = encoderEngine_->GetAudioInputBufferQueue();

    decoderEngine_ = IAVDecoderEngine::Create(sampleInfo.inputFd, decodeCb_);
    decoderEngine_->SetVideoOutputWindow(window);
    isPreview_ = false;
    int32_t ret = decoderEngine_->Init();
    CHECK_AND_RETURN_LOG(ret == 0, "init failed");
    OH_AVFormat *videoFormat = decoderEngine_->GetVideoFormat();
    int64_t avDataDuration;
    OH_AVFormat_GetLongValue(videoFormat, OH_MD_KEY_DURATION, &avDataDuration);
    AVCODEC_SAMPLE_LOGI("avDataDuration is %{public}lld", avDataDuration / 1000);
    decoderEngine_->SetHighPerformance(true);
    decoderEngine_->SetPlayDuration(avDataDuration / 1000);
    if (fd != -1) {
        decoderEngine_->AddAudio(fd);
    }
    decoderEngine_->SetAudioOutputBufferQueue(queue);
}

/**
 * @brief Test for seek while previewing
 */
void PlayerNative::TestPreviewSeek(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("==================== TestPreviewSeek Start ======================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    TestPreviewPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->Start();
    std::this_thread::sleep_for(2s);
    decoderEngine_->Pause();
    std::this_thread::sleep_for(2s);
    decoderEngine_->Seek(0, SEEK_MODE_CLOSEST_SYNC);
    decoderEngine_->Start();
    WaitForPreviewDone();
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("==================== TestPreviewSeek End ========================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for add audio while previewing
 */
void PlayerNative::TestPreviewAddAudio(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================== TestPreviewAddAudio Start ====================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    TestPreviewPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->Start();
    std::this_thread::sleep_for(2s);
    if (fd != -1) {
        decoderEngine_->DeleteAudio(fd);
    }
    std::this_thread::sleep_for(2s);
    if (fd != -1) {
        decoderEngine_->AddAudio(fd);
    }
    WaitForPreviewDone();
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================== TestPreviewAddAudio End ======================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for mute audio while previewing
 */
void PlayerNative::TestPreviewMuteAudio(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================= TestPreviewMuteAudio Start ====================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    TestPreviewPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->Start();
    std::this_thread::sleep_for(2s);
    decoderEngine_->AudioMute(true);
    std::this_thread::sleep_for(2s);
    decoderEngine_->AudioMute(false);
    WaitForPreviewDone();
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================== TestPreviewMuteAudio End =====================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for seek while exporting
 */
void PlayerNative::TestExportSeek(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("===================== TestExportSeek Start ======================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    TestExportPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->Seek(4000, SEEK_MODE_CLOSEST_SYNC);
    encoderEngine_->Start();
    decoderEngine_->Start();
    WaitForExportDone();
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("===================== TestExportSeek End ========================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for add audio while exporting
 */
void PlayerNative::TestExportAddAudio(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================== TestExportAddAudio Start =====================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    TestExportPrepare(sampleInfo, encodeParam, fd);
    encoderEngine_->Start();
    decoderEngine_->Start();
    WaitForExportDone();

    TestExportPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->DeleteAudio(0);
    encoderEngine_->Start();
    decoderEngine_->Start();
    WaitForExportDone();
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================== TestExportAddAudio End =======================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for unselect audio while exporting
 */
void PlayerNative::TestExportUnselectAudio(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================ TestExportUnselectAudio Start ==================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    TestExportPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->AudioUnselect(true);
    decoderEngine_->AudioUnselect(false);
    encoderEngine_->Start();
    decoderEngine_->Start();
    WaitForExportDone();

    TestExportPrepare(sampleInfo, encodeParam, fd);
    decoderEngine_->AudioUnselect(true);
    encoderEngine_->Start();
    decoderEngine_->Start();
    WaitForExportDone();
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("=============== TestExportUnselectAudio End =====================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for different audio format while previewing
 */
void PlayerNative::TestPreviewDifferentAudioFormats(const SampleInfo &sampleInfo,
    const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("============ TestPreviewDifferentAudioFormats Start =============");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    for (int32_t i = 1; i < TEST_DIFFERENT_AUDIO_FORMAT_COUNT; ++i) {
        AVCODEC_SAMPLE_LOGI("====         TestPreviewDifferentAudioFormats: %{public}d        ====", i);
        FILE *fileMusic = nullptr;
        if (i == TEST_AAC_INDEX_FIRST || i == TEST_AAC_INDEX_SECOND) {
            fileMusic = fopen(("data/storage/el2/base/haps/app/files/test" + std::to_string(i) + ".aac").c_str(), "r");
        } else {
            fileMusic = fopen(("data/storage/el2/base/haps/app/files/test" + std::to_string(i) + ".mp3").c_str(), "r");
        }
        int32_t fdMusic = -1;
        CHECK_AND_RETURN_LOG(fileMusic, "open failed");
        fdMusic = fileno(fileMusic);
        
        TestPreviewPrepare(sampleInfo, encodeParam, fdMusic);
        decoderEngine_->Start();
        WaitForPreviewDone();
        if (fileMusic) {
            fclose(fileMusic);
        }
    }
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("============ TestPreviewDifferentAudioFormats End ===============");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for different audio format while exporting
 */
void PlayerNative::TestExportDifferentAudioFormats(const SampleInfo &sampleInfo,
    const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("============ TestExportDifferentAudioFormats Start ==============");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    for (int32_t i = 1; i < TEST_DIFFERENT_AUDIO_FORMAT_COUNT; ++i) {
        AVCODEC_SAMPLE_LOGI("====     TestExportDifferentAudioFormats: %{public}d     ====", i);
        
        FILE *fileMusic = nullptr;
        if (i == TEST_AAC_INDEX_FIRST || i == TEST_AAC_INDEX_SECOND) {
            fileMusic = fopen(("data/storage/el2/base/haps/app/files/test" + std::to_string(i) + ".aac").c_str(), "r");
        } else {
            fileMusic = fopen(("data/storage/el2/base/haps/app/files/test" + std::to_string(i) + ".mp3").c_str(), "r");
        }
        int32_t fdMusic = -1;
        CHECK_AND_RETURN_LOG(fileMusic, "open failed");
        fdMusic = fileno(fileMusic);
        
        FILE *fileResult = fopen(("data/storage/el2/base/haps/app/files/result" + std::to_string(i) + ".mp4").c_str(),
            "wb");
        int32_t fdResult = -1;
        CHECK_AND_RETURN_LOG(fileResult, "open failed");
        fdResult = fileno(fileResult);
        AVEncodeParam encodeParamCopy = encodeParam;
        encodeParamCopy.muxerParam.targetFileFd = fdResult;
        
        TestExportPrepare(sampleInfo, encodeParamCopy, fdMusic);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        if (fileMusic) {
            fclose(fileMusic);
        }
        if (fileResult) {
            fclose(fileResult);
        }
    }
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("============ TestExportDifferentAudioFormats End ================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for long time previewing
 */
void PlayerNative::TestLongTimePreviewing(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================= TestLongTimePreviewing Start ==================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    int32_t reInitTime = 0;
    while (reInitTime < REINIT_TIMES) {
        AVCODEC_SAMPLE_LOGI("====               TestLongTimePreviewing: %{public}d                 ====", reInitTime);
        TestPreviewPrepare(sampleInfo, encodeParam, fd);
        decoderEngine_->Start();
        WaitForPreviewDone();
        reInitTime++;
    }
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================ TestLongTimePreviewing End =====================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for long time exporting
 */
void PlayerNative::TestLongTimeExporting(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================== TestLongTimeExporting Start ==================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    int32_t reInitTime = 0;
    while (reInitTime < REINIT_TIMES) {
        AVCODEC_SAMPLE_LOGI("====              TestLongTimeExporting: %{public}d                   ====", reInitTime);
        TestExportPrepare(sampleInfo, encodeParam, fd);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        reInitTime++;
    }
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================= TestLongTimeExporting End =====================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for long time previewing and exporting concurrently
 */
void PlayerNative::TestLongTimeConcurrency(const SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================== TestLongTimeConcurrency Start ================");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    FILE *fileResourceConcurrency = fopen("data/storage/el2/base/haps/app/files/resource.MP4", "r");
    int32_t fdResourceConcurrency = -1;
    if (fileResourceConcurrency != nullptr) {
        fdResourceConcurrency = fileno(fileResourceConcurrency);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }
    
    FILE *fileMusicConcurrency = fopen("data/storage/el2/base/haps/app/files/2_concurrency.mp3", "r");
    int32_t fdMusicConcurrency = -1;
    if (fileMusicConcurrency != nullptr) {
        fdMusicConcurrency = fileno(fileMusicConcurrency);
    } else {
        AVCODEC_SAMPLE_LOGI("open failed");
    }

    decodeCbConcurrency_ = std::make_shared<DecodeCallbackConcurrency>();
    encodeCbConcurrency_ = std::make_shared<EncodeCallback>();
    
    std::thread t1 = std::thread([fdResourceConcurrency, fdMusicConcurrency]() {
        int32_t reInitTimeConcurrency = 0;
        while (reInitTimeConcurrency < REINIT_TIMES) {
            AVCODEC_SAMPLE_LOGI("====                         TestLongTimeConcurrency: %{public}d                   ====", reInitTimeConcurrency);
            decoderEngineConcurrency_ = IAVDecoderEngine::Create(fdResourceConcurrency, decodeCbConcurrency_);
            decoderEngineConcurrency_->SetVideoOutputWindow(NativeXComponentSample::PluginManager::GetInstance()->pluginWindow_);
            isPreviewConcurrency_ = true;
            int32_t ret = decoderEngineConcurrency_->Init();
            CHECK_AND_BREAK_LOG(ret == 0, "init failed");
            OH_AVFormat *videoFormat = decoderEngineConcurrency_->GetVideoFormat();
            int64_t avDataDuration;
            OH_AVFormat_GetLongValue(videoFormat, OH_MD_KEY_DURATION, &avDataDuration);
            AVCODEC_SAMPLE_LOGI("avDataDuration is %{public}lld", avDataDuration / 1000);
            decoderEngineConcurrency_->SetPlayDuration(avDataDuration / 1000);
            if (fdMusicConcurrency != -1) {
                decoderEngineConcurrency_->AddAudio(fdMusicConcurrency);
            }
            decoderEngineConcurrency_->Start();
            std::unique_lock<std::mutex> lock(catchEOSMutexConcurrency_);
            isEOSCatchedConcurrency_ = false;
            catchEOSCondConcurrency_.wait(lock, []() { return isEOSCatchedConcurrency_; });
            std::this_thread::sleep_for(2s);
            decoderEngineConcurrency_ = nullptr;
            reInitTimeConcurrency++;
        }
    });

    std::thread t2 = std::thread([encodeParam, sampleInfo, fd]() {
        int32_t reInitTime = 0;
        while (reInitTime < REINIT_TIMES) {
            encoderEngine_ = IAVEncoderEngine::Create(encodeParam, encodeCb_);
            encoderEngine_->Init();
            OHNativeWindow *window = encoderEngine_->GetVideoInputWindow();
            std::shared_ptr<PcmBufferQueue> queue = encoderEngine_->GetAudioInputBufferQueue();

            decoderEngine_ = IAVDecoderEngine::Create(sampleInfo.inputFd, decodeCb_);
            decoderEngine_->SetVideoOutputWindow(window);
            isPreview_ = false;
            int32_t ret = decoderEngine_->Init();
            CHECK_AND_BREAK_LOG(ret == 0, "init failed");
            OH_AVFormat *videoFormat = decoderEngine_->GetVideoFormat();
            int64_t avDataDuration;
            OH_AVFormat_GetLongValue(videoFormat, OH_MD_KEY_DURATION, &avDataDuration);
            AVCODEC_SAMPLE_LOGI("avDataDuration is %{public}lld", avDataDuration / 1000);
            decoderEngine_->SetHighPerformance(true);
            decoderEngine_->SetPlayDuration(avDataDuration / 1000);
            if (fd != -1) {
                decoderEngine_->AddAudio(fd);
            }
            decoderEngine_->SetAudioOutputBufferQueue(queue);
            encoderEngine_->Start();
            decoderEngine_->Start();
            std::unique_lock<std::mutex> lock(catchEOSMutex_);
            isEOSCatched_ = false;
            catchEOSCond_.wait(lock, []() { return isEOSCatched_; });
            std::this_thread::sleep_for(2s);
            encoderEngine_ = nullptr;
            decoderEngine_ = nullptr;
            reInitTime++;
        }
    });
    t1.join();
    t2.join();

    if (fileResourceConcurrency) {
        fclose(fileResourceConcurrency);
    }
    if (fileMusicConcurrency) {
        fclose(fileMusicConcurrency);
    }
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================= TestLongTimeConcurrency End ===================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for different video format previewing
 */
void PlayerNative::TestDiffVideoFormatPreviewing(SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================= TestDiffVideoFormatPreviewing Start ==================");
    // SDR
    AVCODEC_SAMPLE_LOGI("====             SDR TestDiffVideoFormatPreviewing start                 ====");
    {
        FILE *sdrFile = fopen("data/storage/el2/base/haps/app/files/sdr.mp4", "r");
        int32_t sdrFd = -1;
        if (sdrFile != nullptr) {
            sdrFd = fileno(sdrFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = sdrFd;
        TestPreviewPrepare(sampleInfo, encodeParam, fd);
        decoderEngine_->SetHighPerformance(true);
        decoderEngine_->Start();
        WaitForPreviewDone();
        if (sdrFile) {
            fclose(sdrFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====             SDR TestDiffVideoFormatPreviewing end                 ====");
    AVCODEC_SAMPLE_LOGI("====             HDRVivid TestDiffVideoFormatPreviewing start                 ====");
    // HDRVivid
    {
        FILE *hdrVividFile = fopen("data/storage/el2/base/haps/app/files/hdrvivid.mp4", "r");
        int32_t hdrVividFd = -1;
        if (hdrVividFile != nullptr) {
            hdrVividFd = fileno(hdrVividFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = hdrVividFd;
        TestPreviewPrepare(sampleInfo, encodeParam, fd);
        decoderEngine_->SetHighPerformance(true);
        decoderEngine_->Start();
        WaitForPreviewDone();
        if (hdrVividFile) {
            fclose(hdrVividFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====             HDRVivid TestDiffVideoFormatPreviewing end                 ====");
    AVCODEC_SAMPLE_LOGI("====             Dolby TestDiffVideoFormatPreviewing start                 ====");
    // Dolby
    {
        FILE *dolbyyFile = fopen("data/storage/el2/base/haps/app/files/dolby.MOV", "r");
        int32_t dolbyFd = -1;
        if (dolbyyFile != nullptr) {
            dolbyFd = fileno(dolbyyFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = dolbyFd;
        TestPreviewPrepare(sampleInfo, encodeParam, fd);
        decoderEngine_->SetHighPerformance(true);
        decoderEngine_->Start();
        WaitForPreviewDone();
        if (dolbyyFile) {
            fclose(dolbyyFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====            Dolby TestDiffVideoFormatPreviewing end                 ====");
    AVCODEC_SAMPLE_LOGI("====           HDR10 TestDiffVideoFormatPreviewing start                 ====");
    // HDR10
    {
        FILE *dhr10File = fopen("data/storage/el2/base/haps/app/files/hdr10.mp4", "r");
        int32_t hdr10Fd = -1;
        if (dhr10File != nullptr) {
            hdr10Fd = fileno(dhr10File);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = hdr10Fd;
        TestPreviewPrepare(sampleInfo, encodeParam, fd);
        decoderEngine_->SetHighPerformance(true);
        decoderEngine_->Start();
        WaitForPreviewDone();
        if (dhr10File) {
            fclose(dhr10File);
        }
    }
    AVCODEC_SAMPLE_LOGI("====           HDR10 TestDiffVideoFormatPreviewing end                 ====");
    AVCODEC_SAMPLE_LOGI("====           HLG TestDiffVideoFormatPreviewing start                 ====");
    // HLG
    {
        FILE *hlgFile = fopen("data/storage/el2/base/haps/app/files/hlg.mp4", "r");
        int32_t hlgFd = -1;
        if (hlgFile != nullptr) {
            hlgFd = fileno(hlgFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = hlgFd;
        TestPreviewPrepare(sampleInfo, encodeParam, fd);
        decoderEngine_->SetHighPerformance(true);
        decoderEngine_->Start();
        WaitForPreviewDone();
        if (hlgFile) {
            fclose(hlgFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====           HLG TestDiffVideoFormatPreviewing end                 ====");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================ TestLongTimePreviewing End =====================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}

/**
 * @brief Test for long time exporting
 */
void PlayerNative::TestDiffVideoFormatExporting(SampleInfo &sampleInfo, const AVEncodeParam& encodeParam, int32_t fd)
{
    AVCODEC_SAMPLE_LOGI("=================================================================");
    AVCODEC_SAMPLE_LOGI("================== TestDiffVideoFormatExporting Start ==================");
    AVCODEC_SAMPLE_LOGI("====           SDR TestDiffVideoFormatExporting start                 ====");
    // SDR
    {
        FILE *sdrFile = fopen("data/storage/el2/base/haps/app/files/sdr.mp4", "r");
        int32_t sdrFd = -1;
        if (sdrFile != nullptr) {
            sdrFd = fileno(sdrFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = sdrFd;
        TestExportPrepare(sampleInfo, encodeParam, fd);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        if (sdrFile) {
            fclose(sdrFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====           SDR TestDiffVideoFormatExporting end                 ====");
    AVCODEC_SAMPLE_LOGI("====           HDRVivid TestDiffVideoFormatExporting start                 ====");
    // HDRVivid
    {
        FILE *hdrVividFile = fopen("data/storage/el2/base/haps/app/files/hdrvivid.mp4", "r");
        int32_t hdrVividFd = -1;
        if (hdrVividFile != nullptr) {
            hdrVividFd = fileno(hdrVividFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = hdrVividFd;
        TestExportPrepare(sampleInfo, encodeParam, fd);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        if (hdrVividFile) {
            fclose(hdrVividFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====           HDRVivid TestDiffVideoFormatExporting end                 ====");
    AVCODEC_SAMPLE_LOGI("====           Dolby TestDiffVideoFormatExporting start                 ====");
    // Dolby
    {
        FILE *dolbyyFile = fopen("data/storage/el2/base/haps/app/files/dolby.MOV", "r");
        int32_t dolbyFd = -1;
        if (dolbyyFile != nullptr) {
            dolbyFd = fileno(dolbyyFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        SampleInfo sampleInfoTmp = sampleInfo;
        sampleInfoTmp.inputFd = dolbyFd;
        TestExportPrepare(sampleInfoTmp, encodeParam, fd);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        if (dolbyyFile) {
            fclose(dolbyyFile);
        }
    }
    AVCODEC_SAMPLE_LOGI("====           Dolby TestDiffVideoFormatExporting end                 ====");
    AVCODEC_SAMPLE_LOGI("====           HDR10 TestDiffVideoFormatExporting start                 ====");
    // HDR10
    {
        FILE *hdr10File = fopen("data/storage/el2/base/haps/app/files/hdr10.mp4", "r");
        int32_t hdr10Fd = -1;
        if (hdr10File != nullptr) {
            hdr10Fd = fileno(hdr10File);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = hdr10Fd;
        TestExportPrepare(sampleInfo, encodeParam, fd);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        if (hdr10File) {
            fclose(hdr10File);
        }
    }
    AVCODEC_SAMPLE_LOGI("====           HDR10 TestDiffVideoFormatExporting end                 ====");
    AVCODEC_SAMPLE_LOGI("====           HLG TestDiffVideoFormatExporting start                 ====");
    // HLG
    {
        FILE *hlgFile = fopen("data/storage/el2/base/haps/app/files/hlg.mp4", "r");
        int32_t hlgFd = -1;
        if (hlgFile != nullptr) {
            hlgFd = fileno(hlgFile);
        } else {
            AVCODEC_SAMPLE_LOGI("open failed");
            return;
        }
        sampleInfo.inputFd = hlgFd;
        TestExportPrepare(sampleInfo, encodeParam, fd);
        encoderEngine_->Start();
        decoderEngine_->Start();
        WaitForExportDone();
        if (hlgFile) {
            fclose(hlgFile);
        }
    }
     AVCODEC_SAMPLE_LOGI("====           HLG TestDiffVideoFormatExporting end                 ====");
    AVCODEC_SAMPLE_LOGI("====                                                         ====");
    AVCODEC_SAMPLE_LOGI("================= TestDiffVideoFormatExporting End =====================");
    AVCODEC_SAMPLE_LOGI("=================================================================");
}


int32_t g_restartTime = 0;
void DecodeCallback::OnDecodeStatus(CodecStatus status)
{
    if (status == CodecStatus::END_OF_STREAM) {
        AVCODEC_SAMPLE_LOGI("EOS catched");
        if (PlayerNative::isPreview_) {
            if (g_restartTime < RESTART_TIMES) {
                std::thread([]() {
                    PlayerNative::decoderEngine_->Seek(0, OH_AVSeekMode::SEEK_MODE_CLOSEST_SYNC);
                    PlayerNative::decoderEngine_->Start();
                    g_restartTime++;
                }).detach();
            } else {
                std::thread([]() {
                    PlayerNative::decoderEngine_->Stop();
                    g_restartTime = 0;
                    std::unique_lock<std::mutex> lock(PlayerNative::catchEOSMutex_);
                    PlayerNative::isEOSCatched_ = true;
                    PlayerNative::catchEOSCond_.notify_all();
                }).detach();
            }
        } else {
            std::thread([]() {
                PlayerNative::encoderEngine_->SendEOS();
                std::unique_lock<std::mutex> lock(PlayerNative::catchEOSMutex_);
                PlayerNative::isEOSCatched_ = true;
                PlayerNative::catchEOSCond_.notify_all();
            }).detach();
        }
    }
}

int32_t g_restartTimeConcurrency = 0;
void DecodeCallbackConcurrency::OnDecodeStatus(CodecStatus status)
{
    if (status == CodecStatus::END_OF_STREAM) {
        AVCODEC_SAMPLE_LOGI("EOS catched");
        if (PlayerNative::isPreviewConcurrency_) {
            if (g_restartTimeConcurrency < RESTART_TIMES) {
                std::thread([]() {
                    PlayerNative::decoderEngineConcurrency_->Seek(0, OH_AVSeekMode::SEEK_MODE_CLOSEST_SYNC);
                    PlayerNative::decoderEngineConcurrency_->Start();
                    g_restartTimeConcurrency++;
                }).detach();
            } else {
                std::thread([]() {
                    PlayerNative::decoderEngineConcurrency_->Stop();
                    g_restartTimeConcurrency = 0;
                    std::unique_lock<std::mutex> lock(PlayerNative::catchEOSMutexConcurrency_);
                    PlayerNative::isEOSCatchedConcurrency_ = true;
                    PlayerNative::catchEOSCondConcurrency_.notify_all();
                }).detach();
            }
        } else {
            std::thread([]() {
                PlayerNative::encoderEngineConcurrency_->SendEOS();
                std::unique_lock<std::mutex> lock(PlayerNative::catchEOSMutexConcurrency_);
                PlayerNative::isEOSCatchedConcurrency_ = true;
                PlayerNative::catchEOSCondConcurrency_.notify_all();
            }).detach();
        }
    }
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor classProp[] = {
        {"playNative", nullptr, PlayerNative::Play, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"exportNative", nullptr, PlayerNative::Export, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"testNative", nullptr, PlayerNative::Test, nullptr, nullptr, nullptr, napi_default, nullptr},
    };

    NativeXComponentSample::PluginManager::GetInstance()->Export(env, exports);
    napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[0]), classProp);
    return exports;
}
EXTERN_C_END

static napi_module PlayerModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "player",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterPlayerModule(void) { napi_module_register(&PlayerModule); }